Stage 3 結尾證明了「管理筆記」這件事——分類、補齊 Frontmatter、自動織入連結、跨指令銜接——全部串起來也沒問題。但截至目前,這些能力回答的問題都是「這一篇筆記怎麼樣」,沒有一個指令能回答「整個知識庫長什麼樣子」。
Stage 4(Day 20-25)要做的事,是把 vault 從一堆各自獨立驗證過的筆記,提升到一張可以整體分析的知識圖譜。Day21 就要讓 brain-cli 產出 Graphify 能吃的 JSON,Day22 接著在這張圖上找核心樞紐與斷點。但在動手定義 JSON schema 之前,「Node 是什麼」「Edge 是什麼」「Graphify 的資料格式為什麼要長那樣」這幾個問題如果沒先講清楚,Day21 的 proposal 會在讀者還沒有共同詞彙的情況下,就直接丟出一份 JSON 結構,讀者沒辦法判斷這個結構為什麼是對的。
今天不寫程式碼,也不碰 obsidian-agent-brain。這是一篇純概念文章,比照 Day13 開場 Stage 3 的做法,目標是建立 Stage 4 剩下五天要共用的詞彙表。
先講最基本的對應關係:Node(節點) 對應 vault 裡的一篇筆記,Edge(邊) 對應正文裡的一條 [[Wikilink]],Graph(圖) 就是所有 Node 與 Edge 的集合——沒有更多了。
用一個具體例子看:00_Areas/Cobra CLI 框架.md 這篇筆記本身,對應圖論裡的一個 Node;如果它的正文裡有一條 [[Golang CLI 開發參考資料]],這條 Wikilink 就對應一條 Edge,方向是從「Cobra CLI 框架」指向「Golang CLI 開發參考資料」。把 vault 裡所有筆記都當成 Node、所有 Wikilink 都當成 Edge 蒐集起來,蒐集出來的整個集合,就是這座 vault 的知識圖譜。
這個對應關係之所以值得專門講一次,是因為它其實不是新東西——Day10 建的 Index、Day16 的自動織入,操作的一直都是「筆記」跟「Wikilink」這兩種實體,只是從來沒有人明確說過「這兩種實體,其實就是圖論裡的 Node 和 Edge」。今天要做的,就是把這層對應關係講明白,讓後面五天可以直接用「Node」「Edge」這兩個詞,不用每次都繞回「筆記」跟「Wikilink」重新解釋一次。
圖論把邊分成兩種:有向邊(directed edge) 有明確的方向,從來源指向目標,兩個方向不能互換;無向邊(undirected edge) 沒有方向之分,兩端是對等的。
單向 [[Wikilink]] 本質上就是一條有向邊——如果筆記 A 用 [[B]] 連到筆記 B,但 B 沒有任何連結指回 A,這條邊只能從 A 走到 B,走不回來。如果之後想讓它變成雙向,需要的是額外新增一條從 B 指向 A 的邊,而不是把原本那條邊「改成」無向邊。
這裡有一個容易被誤解的地方,跟 Day16 已經實作的自動雙向織入直接相關:兩篇筆記透過自動織入互相連結之後,效果上確實變成「兩篇筆記可以互相到達」,看起來很像一條無向邊。但底層資料其實不是這樣——自動織入做的事,是在圖上額外補上一條方向相反的有向邊,兩條邊各自獨立存在,分別記錄在對應筆記各自的 outbound 連結裡,並不會合併成一條特殊的無向邊。
這個區分不是咬文嚼字,而是直接對應到 Day10 Index 的實際資料結構:Index.Outbound 跟 Index.Inbound 是兩張分開的表,不是一張無向邊的集合表。一篇筆記的 outbound 連結清單裡有什麼、另一篇筆記的 inbound 連結清單裡有什麼,是各自獨立記錄的兩件事,只是在自動織入生效之後,這兩份記錄剛好互相對應而已。如果誤以為雙向連結就是無向邊、兩者可以互換,日後看到 Day21 的 JSON schema 把一組雙向連結匯出成兩條獨立的邊,會覺得多此一舉——但那正是底層資料的真實樣貌。
Graphify 作為圖形視覺化/分析工具,要能正確畫出並辨識一張圖,對輸入資料的欄位有最基本的期待:每個節點至少需要一個穩定且唯一的識別碼(identifier)與一個可讀的顯示名稱(label);每條邊至少需要指出來源節點與目標節點的識別碼(source/target)。
道理很直接:如果節點的識別碼不唯一,工具沒辦法判斷兩筆資料是同一個節點還是撞名的兩個節點;如果邊指向的識別碼在節點清單裡根本不存在,工具也沒辦法把這條邊畫在圖上正確的兩個端點之間。這是任何圖形工具都繞不開的最小需求,不是 Graphify 這個工具特有的規定。
今天刻意不往下追究 Graphify 特定版本的完整 JSON schema——欄位具體叫什麼名字、要不要支援節點顏色或分組、檔案要輸出成什麼結構,這些都留到 Day21 動手寫匯出邏輯時才定案。原因很單純:具體工具版本的欄位清單本來就該在真正要寫程式的時候查閱決定,寫在這篇純概念文章裡,反而可能跟 Day21 實際採用的版本不一致而過時。如果現在很好奇「Day21 匯出的 JSON 裡,節點的識別碼欄位具體會叫做什麼名字」——這篇文章不會給答案,因為那個答案本來就還沒定案,讀完今天,應該建立的是「需要有唯一識別碼這個欄位」的通用道理,不是具體欄位名稱。
Index 對上號:Day21 的資料從哪裡來把上面兩段的概念放回 Day10 已經建好的 Index 結構,答案幾乎是現成的。
Index 的標題/別名查找表(byTitleOrAlias,對外透過 Resolve 查詢)記錄的是「每篇筆記的標題與別名」——這正好可以直接拿來當節點的唯一識別碼與可讀顯示名稱:一篇筆記本身在 vault 裡就有一個穩定的身分(可以是檔案路徑或標題),它的 Title 就是現成的 label。Index.Outbound 跟 Index.Inbound 這兩張連結圖,記錄的是「哪篇筆記連到哪篇筆記」——這正好可以直接轉換成邊的集合,每一筆 outbound 連結就是一條邊的來源與目標。
也就是說,Day21 要讓 brain-cli 匯出 Graphify JSON,最合理的資料來源就是 Day10 已經建好的 Index,把它轉換成 Graphify 期待的格式就好,不需要重新掃描 vault,也不需要重新設計一套新的內部資料結構去記錄節點跟邊。今天建立的 Node/Edge 詞彙,跟 Day10 的 Index 之間這條對應線,就是 Day21 proposal 可以直接站上去的地基。
Stage 4 從今天正式開場:Node、Edge、Graph 的基礎對應關係,有向圖與無向圖在雙向連結底層的真實差異,Graphify 資料格式的最小欄位期待,以及這一切跟 Day10 Index 之間的對應關係——這四塊拼起來,就是接下來五天要共用的詞彙表跟心智模型。
👉 明天 Day21,我們要真正動手,讓 brain-cli 把 Index 轉換成 Graphify 能吃的 JSON,把今天講的概念,變成看得到的知識圖譜檔案。我們明天見!